Translate links into the form "title (link)" when sending over MSN without OTR, as...
[adiumx.git] / Plugins / Purple Service / ESPurpleMSNAccount.m
blob06c37122ec6db2ea758a41a79a4a0dc1a13b29cb
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
5  * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6  * General Public License as published by the Free Software Foundation; either version 2 of the License,
7  * or (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11  * Public License for more details.
12  * 
13  * You should have received a copy of the GNU General Public License along with this program; if not,
14  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  */
17 #import "ESPurpleMSNAccount.h"
19 #import <libpurple/state.h>
21 #import <Adium/AIAccountControllerProtocol.h>
22 #import <Adium/AIContactControllerProtocol.h>
23 #import <Adium/AIContentControllerProtocol.h>
24 #import <Adium/AIStatusControllerProtocol.h>
25 #import <Adium/AIAccount.h>
26 #import <Adium/AIHTMLDecoder.h>
27 #import <Adium/AIListContact.h>
28 #import <Adium/AIService.h>
29 #import <Adium/AIStatus.h>
30 #import <Adium/ESFileTransfer.h>
31 #import <AIUtilities/AIAttributedStringAdditions.h>
32 #import <AIUtilities/AIMutableStringAdditions.h>
33 #import <AIUtilities/AIStringAdditions.h>
34 #import <FriBidi/NSString-FBAdditions.h>
36 #import <libpurple/msn.h>
38 #define DEFAULT_MSN_PASSPORT_DOMAIN                             @"@hotmail.com"
39 #define SECONDS_BETWEEN_FRIENDLY_NAME_CHANGES   10
41 #if !NEW_MSN
42         extern void msn_set_friendly_name(PurpleConnection *gc, const char *entry);
43 #endif
45 @interface ESPurpleMSNAccount (PRIVATE)
46 - (void)updateFriendlyNameAfterConnect;
47 - (void)setServersideDisplayName:(NSString *)friendlyName;
48 @end
50 @implementation ESPurpleMSNAccount
52 /*!
53  * @brief The UID will be changed. The account has a chance to perform modifications
54  *
55  * For example, MSN adds @hotmail.com to the proposedUID and returns the new value
56  *
57  * @param proposedUID The proposed, pre-filtered UID (filtered means it has no characters invalid for this servce)
58  * @result The UID to use; the default implementation just returns proposedUID.
59  */
60 - (NSString *)accountWillSetUID:(NSString *)proposedUID
62         NSString        *correctUID;
63         
64         if (([proposedUID length] > 0) && 
65            ([proposedUID rangeOfString:@"@"].location == NSNotFound)) {
66                 correctUID = [proposedUID stringByAppendingString:DEFAULT_MSN_PASSPORT_DOMAIN];
67         } else {
68                 correctUID = proposedUID;
69         }
70         
71         return correctUID;
74 - (void)initAccount
76         [super initAccount];
77         lastFriendlyNameChange = nil;
79         [[adium preferenceController] registerPreferenceObserver:self forGroup:PREF_GROUP_MSN_SERVICE];
82 - (void)dealloc {
83         [[adium preferenceController] unregisterPreferenceObserver:self];
84         
85         [lastFriendlyNameChange release];
86         [queuedFriendlyName release];
88         [super dealloc];
91 - (const char*)protocolPlugin
93     return "prpl-msn";
96 - (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
98         NSString *msg = [super encodedAttributedStringForSendingContentMessage:inContentMessage];
99         
100         if (msg) {
101                 /* If our message contains RTL string we shall surround it with a span tag
102                  * with proper dir attribute so libpurple can apply the MSN writing direction
103                  * flag. Note that we must check the string of the content message and not the
104                  * one returned by our superclass as it appends its own html to the string.
105                  * Only the content message can tell us the original direction.
106                  */
107                 return (([[[inContentMessage message] string] baseWritingDirection] == NSWritingDirectionRightToLeft)
108                                 ? [NSString stringWithFormat:@"<span dir=\"rtl\">%@</span>", msg]
109                                 : msg);
110         } else {
111                 return nil;
112         }
115 #pragma mark Connection
116 - (void)configurePurpleAccount
118         [super configurePurpleAccount];
119         
120         BOOL HTTPConnect = [[self preferenceForKey:KEY_MSN_HTTP_CONNECT_METHOD group:GROUP_ACCOUNT_STATUS] boolValue];
121         purple_account_set_bool(account, "http_method", HTTPConnect);
124 - (NSString *)connectionStringForStep:(int)step
126         switch (step)
127         {
128 #if NEW_MSN
129                 case 0:
130                         return AILocalizedString(@"Connecting",nil);
131                         break;
132                 case 1:
133                         return AILocalizedString(@"Handshaking",nil);
134                         break;                  
135                 case 2:
136                         return AILocalizedString(@"Transferring",nil);
137                         break;
138                 case 3:
139                         return AILocalizedString(@"Handshaking",nil);
140                         break;
141                 case 4:
142                         return AILocalizedString(@"Starting authentication",nil);
143                         break;
144                 case 5:
145                         return AILocalizedString(@"Getting Cookie",nil);
146                         break;
147                 case 6:
148                         return AILocalizedString(@"Authenticating",nil);
149                         break;
150                 case 7:
151                         return AILocalizedString(@"Sending Cookie",nil);
152                         break;
153                 case 8:
154                         return AILocalizedString(@"Retrieving buddy list",nil);
155                         break;
156 #else
157                 case 0:
158                         return AILocalizedString(@"Connecting",nil);
159                         break;
160                 case 1:
161                         return AILocalizedString(@"Connecting",nil);
162                         break;
163                 case 2:
164                         return AILocalizedString(@"Syncing with server",nil);
165                         break;                  
166                 case 3:
167                         return AILocalizedString(@"Requesting to send password",nil);
168                         break;
169                 case 4:
170                         return AILocalizedString(@"Syncing with server",nil);
171                         break;
172                 case 5:
173                         return AILocalizedString(@"Requesting to send password",nil);
174                         break;
175                 case 6:
176                         return AILocalizedString(@"Password sent",nil);
177                         break;
178                 case 7:
179                         return AILocalizedString(@"Retrieving buddy list",nil);
180                         break;
181 #endif
182         }
183         return nil;
186 - (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
188         return [AIHTMLDecoder encodeHTML:inAttributedString
189                                                          headers:NO
190                                                         fontTags:YES
191                                   includingColorTags:YES
192                                            closeFontTags:YES
193                                                    styleTags:YES
194                   closeStyleTagsOnFontChange:YES
195                                           encodeNonASCII:NO
196                                                 encodeSpaces:NO
197                                                   imagesPath:nil
198                                    attachmentsAsText:YES
199                    onlyIncludeOutgoingImages:NO
200                                           simpleTagsOnly:YES
201                                           bodyBackground:NO
202                                  allowJavascriptURLs:YES];
205 - (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
207         /* If we're sending a message on an encryption chat, we can encode the HTML normally, as links will go through fine.
208          * If we're sending a message normally, MSN will drop the title of any link, so we preprocess it to be in the form "title (link)"
209          */
210         return [AIHTMLDecoder encodeHTML:([[inContentMessage chat] isSecure] ? inAttributedString : [inAttributedString attributedStringByConvertingLinksToStrings])
211                                                          headers:NO
212                                                         fontTags:YES
213                                   includingColorTags:YES
214                                            closeFontTags:YES
215                                                    styleTags:YES
216                   closeStyleTagsOnFontChange:YES
217                                           encodeNonASCII:NO
218                                                 encodeSpaces:NO
219                                                   imagesPath:nil
220                                    attachmentsAsText:YES
221                    onlyIncludeOutgoingImages:NO
222                                           simpleTagsOnly:YES
223                                           bodyBackground:NO
224                                  allowJavascriptURLs:YES];
227 #pragma mark Status
228 //Update our full name on connect
229 - (oneway void)accountConnectionConnected
231         [super accountConnectionConnected];
232         
233         [self updateFriendlyNameAfterConnect];
234 }       
237  * @brief Update our friendly name to match the server friendly name if appropriate
239  * Well behaved MSN clients respect the serverside display name so that an update on one client is reflected on another.
240  * 
241  * If our display name is static and specified specifically for our account, we should update to the serverside one if they aren't the same.
243  * However, if our display name is dynamic, most likely we're looking at the filtered version of our dynamic
244  * name, so we shouldn't update to the filtered one.  Furthermore, if our display name is set at the Aduim-global level,
245  * we should use that name, not whatever is specified by the last client to connect.
246  */
247 - (void)updateFriendlyNameAfterConnect
249         const char                      *displayName = purple_connection_get_display_name(purple_account_get_connection(account));
250         NSAttributedString      *accountDisplayName = [[self preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
251                                                                                                                    group:GROUP_ACCOUNT_STATUS
252                                                                                    ignoreInheritedValues:YES] attributedString];
253         NSAttributedString      *globalPreference = [[self preferenceForKey:KEY_ACCOUNT_DISPLAY_NAME
254                                                                                                                           group:GROUP_ACCOUNT_STATUS
255                                                                                           ignoreInheritedValues:NO] attributedString];
256         BOOL                            accountDisplayNameChanged = NO;
257         BOOL                            shouldUpdateDisplayNameImmediately= NO;
259         /* If the friendly name changed since the last time we connected (the user changed it while offline)
260          * set it serverside and clear the flag.
261          */
262         if ((accountDisplayName && (accountDisplayNameChanged = [[self preferenceForKey:KEY_MSN_DISPLAY_NAMED_CHANGED group:GROUP_ACCOUNT_STATUS] boolValue])) ||
263                 (!accountDisplayName && globalPreference)) {
264                 shouldUpdateDisplayNameImmediately = YES;
266                 if (accountDisplayNameChanged) {
267                         [self setPreference:nil
268                                                  forKey:KEY_MSN_DISPLAY_NAMED_CHANGED
269                                                   group:GROUP_ACCOUNT_STATUS];
270                 }
272         } else {
273                 /* If our locally set friendly name didn't change since the last time we connected but one is set,
274                  * we want to update to the serverside settings as appropriate.
275                  *
276                  * An important exception is if our per-account display name is dynamic (i.e. a 'Now Playing in iTunes' name).
277                  *
278                  * We explicitly ignore any display name starting with "<msnobj" because purple_connection_get_display_name() occassionaly (rarely)
279                  * returns invalid data starting with that string.  The user can still set this as an MSN display name if she is really that weird, but
280                  * we won't update to match other clients setting it.
281                  */
282                 if (displayName &&
283                         strncmp(displayName, "<msnobj", 7),
284                         strcmp(displayName, [[self UID] UTF8String]) &&
285                         strcmp(displayName, [[self formattedUID] UTF8String])) {
286                         /* There is a serverside display name, and it's not the same as our UID. */
287                         const char                      *accountDisplayNameUTF8String = [[accountDisplayName string] UTF8String];
288                         
289                         if (accountDisplayNameUTF8String &&
290                                 strcmp(accountDisplayNameUTF8String, displayName)) {
291                                 /* The display name is different from our per-account preference, which exists. Check if our preference is static.
292                                  * If the if() above got FALSE, we don't need to do anything; the serverside preference should stand as-is. */
293                                 [[adium contentController] filterAttributedString:accountDisplayName
294                                                                                                   usingFilterType:AIFilterContent
295                                                                                                                 direction:AIFilterOutgoing
296                                                                                                         filterContext:self
297                                                                                                   notifyingTarget:self
298                                                                                                                  selector:@selector(gotFilteredFriendlyName:context:)
299                                                                                                                   context:[NSDictionary dictionaryWithObjectsAndKeys:
300                                                                                                                           accountDisplayName, @"accountDisplayName",
301                                                                                                                           [NSString stringWithUTF8String:displayName], @"displayName",
302                                                                                                                           nil]];
303                         }
305                 } else {
306                         shouldUpdateDisplayNameImmediately = YES;
307                 }
308         }
309         
310         if (shouldUpdateDisplayNameImmediately) {
311                 [self updateStatusForKey:KEY_ACCOUNT_DISPLAY_NAME];
312         }
315 - (void)gotFilteredFriendlyName:(NSAttributedString *)filteredFriendlyName context:(NSDictionary *)infoDict
317         if ((!filteredFriendlyName && [infoDict objectForKey:@"displayName"]) ||
318            ([[filteredFriendlyName string] isEqualToString:[[infoDict objectForKey:@"accountDisplayName"] string]])) {
319                 /* Filtering made no changes to the string, so we're static. If we make it here, update to match the server. */
320                 NSAttributedString      *newPreference;
322                 newPreference = [[NSAttributedString alloc] initWithString:[infoDict objectForKey:@"displayName"]];
324                 [self setPreference:[newPreference dataRepresentation]
325                                          forKey:KEY_ACCOUNT_DISPLAY_NAME
326                                           group:GROUP_ACCOUNT_STATUS];
327                 [newPreference release];
329                 [self updateStatusForKey:KEY_ACCOUNT_DISPLAY_NAME];
331         } else {
332                 //Set it serverside
333                 [self setServersideDisplayName:[filteredFriendlyName string]];
334         }
337 - (void)doQueuedSetServersideDisplayName
339         [self setServersideDisplayName:queuedFriendlyName];
340         [queuedFriendlyName release]; queuedFriendlyName = nil;
343 - (void)setServersideDisplayName:(NSString *)friendlyName
345         if (account && purple_account_is_connected(account)) {          
346                 NSDate *now = [NSDate date];
348                 if (!lastFriendlyNameChange ||
349                         [now timeIntervalSinceDate:lastFriendlyNameChange] > SECONDS_BETWEEN_FRIENDLY_NAME_CHANGES) {
351                         //Don't allow newlines in the friendly name; convert them to slashes.
352                         NSMutableString         *noNewlinesFriendlyName = [[friendlyName mutableCopy] autorelease];
353                         [noNewlinesFriendlyName convertNewlinesToSlashes];
354                         friendlyName = noNewlinesFriendlyName;
356                         /*
357                          * The MSN display name will be URL encoded via purple_url_encode().  The maximum length of the _encoded_ string is
358                          * BUDDY_ALIAS_MAXLEN (387 characters as of purple 2.0.0). We can't simply encode and truncate as we might end up with
359                          * part of an encoded character being cut off, so we instead truncate to smaller and smaller strings and encode, until it fits
360                          */
361                         const char *friendlyNameUTF8String = [friendlyName UTF8String];
362                         int currentMaxNumberOfPreEncodedCharacters = BUDDY_ALIAS_MAXLEN;
364                         while (friendlyNameUTF8String &&
365                                    strlen(purple_url_encode(friendlyNameUTF8String)) > BUDDY_ALIAS_MAXLEN) {
366                                 AILog(@"Shortening because %s (max len %i) [%s] len (%i) > %i",
367                                           friendlyNameUTF8String, currentMaxNumberOfPreEncodedCharacters,
368                                           purple_url_encode(friendlyNameUTF8String),strlen(purple_url_encode(friendlyNameUTF8String)),
369                                           BUDDY_ALIAS_MAXLEN);
370                                 friendlyName = [noNewlinesFriendlyName stringWithEllipsisByTruncatingToLength:currentMaxNumberOfPreEncodedCharacters];                          
371                                 friendlyNameUTF8String = [friendlyName UTF8String];
372                                 currentMaxNumberOfPreEncodedCharacters -= 10;
373                         }
374 #if NEW_MSN
375                         msn_act_id(purple_account_get_connection(account), friendlyNameUTF8String);
376 #else
377                         msn_set_friendly_name(purple_account_get_connection(account), friendlyNameUTF8String);
378 #endif
380                         [lastFriendlyNameChange release];
381                         lastFriendlyNameChange = [now retain];
383                 } else {
384                         [NSObject cancelPreviousPerformRequestsWithTarget:self
385                                                                                                          selector:@selector(doQueuedSetServersideDisplayName)
386                                                                                                            object:nil];
387                         if (queuedFriendlyName != friendlyName) {
388                                 [queuedFriendlyName release];
389                                 queuedFriendlyName = [friendlyName retain];
390                         }
391                         [self performSelector:@selector(doQueuedSetServersideDisplayName)
392                                            withObject:nil
393                                            afterDelay:(SECONDS_BETWEEN_FRIENDLY_NAME_CHANGES - [now timeIntervalSinceDate:lastFriendlyNameChange])];
395                         AILog(@"%@: Queueing serverside display name change to %@ for %0f seconds", self, queuedFriendlyName, (SECONDS_BETWEEN_FRIENDLY_NAME_CHANGES - [now timeIntervalSinceDate:lastFriendlyNameChange]));
396                 }
397         }
401  * @brief Set our serverside 'friendly name'
403  * There is a rate limit on how quickly we can set our friendly name.
405  * @param attributedFriendlyName The new friendly name.  This is used as plaintext; it is an NSAttributedString for generic useage with the autoupdating filtering system.
407  */
408 - (void)gotFilteredDisplayName:(NSAttributedString *)attributedDisplayName
410         NSString        *friendlyName = [attributedDisplayName string];
411         AILog(@"%@: gotFilteredDisplayName: %@ (I am currently %@)",self,friendlyName,[self currentDisplayName]);
413         if (!friendlyName || ![friendlyName isEqualToString:[self currentDisplayName]]) {               
414                 [self setServersideDisplayName:friendlyName];
415         }
416         
417         [super gotFilteredDisplayName:attributedDisplayName];
420 - (BOOL)useDisplayNameAsStatusMessage
422         return displayNamesAsStatus;
425 - (void)updateMobileStatus:(AIListContact *)theContact withData:(BOOL)isMobile
427         //No-op
430 - (BOOL)shouldIncludeNowPlayingInformationInAllStatuses
432         return [[self preferenceForKey:KEY_BROADCAST_MUSIC_INFO group:GROUP_ACCOUNT_STATUS] boolValue];
435 #pragma mark File transfer
436 - (BOOL)canSendFolders
438         return NO;
441 - (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
443         [super _beginSendOfFileTransfer:fileTransfer];
446 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
448     [super acceptFileTransferRequest:fileTransfer];    
451 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
453     [super rejectFileReceiveRequest:fileTransfer];    
456 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
458         [super cancelFileTransfer:fileTransfer];
461 - (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
462                                                         object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
464         [super preferencesChangedForGroup:group key:key object:object preferenceDict:prefDict firstTime:firstTime];
465         
466         if ([group isEqualToString:PREF_GROUP_MSN_SERVICE]) {
467                 displayNamesAsStatus = [[prefDict objectForKey:KEY_MSN_DISPLAY_NAMES_AS_STATUS] boolValue];
468         }
471 #pragma mark Status messages
473 - (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
475         NSString                *statusName = nil;
476         PurplePresence  *presence = purple_buddy_get_presence(buddy);
477         PurpleStatus            *status = purple_presence_get_active_status(presence);
478         const char              *purpleStatusID = purple_status_get_id(status);
480         if (!purpleStatusID) return nil;
482         if (!strcmp(purpleStatusID, "brb")) {
483                 statusName = STATUS_NAME_BRB;
484                 
485         } else if (!strcmp(purpleStatusID, "busy")) {
486                 statusName = STATUS_NAME_BUSY;
487                 
488         } else if (!strcmp(purpleStatusID, "phone")) {
489                 statusName = STATUS_NAME_PHONE;
490                 
491         } else if (!strcmp(purpleStatusID, "lunch")) {
492                 statusName = STATUS_NAME_LUNCH;
493                 
494         } else if (!strcmp(purpleStatusID, "invisible")) {
495                 statusName = STATUS_NAME_INVISIBLE;             
496         }
497         
498         return statusName;
502  * @brief Return the purple status ID to be used for a status
504  * Most subclasses should override this method; these generic values may be appropriate for others.
506  * Active services provided nonlocalized status names.  An AIStatus is passed to this method along with a pointer
507  * to the status message.  This method should handle any status whose statusNname this service set as well as any statusName
508  * defined in  AIStatusController.h (which will correspond to the services handled by Adium by default).
509  * It should also handle a status name not specified in either of these places with a sane default, most likely by loooking at
510  * [statusState statusType] for a general idea of the status's type.
512  * @param statusState The status for which to find the purple status ID
513  * @param arguments Prpl-specific arguments which will be passed with the state. Message is handled automatically.
515  * @result The purple status ID
516  */
517 - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState
518                                                         arguments:(NSMutableDictionary *)arguments
520         const char              *statusID = NULL;
521         NSString                *statusName = [statusState statusName];
522         NSString                *statusMessageString = [statusState statusMessageString];
524         if (!statusMessageString) statusMessageString = @"";
526         switch ([statusState statusType]) {
527                 case AIAvailableStatusType:
528                         break;
530                 case AIAwayStatusType:
531                         if (([statusName isEqualToString:STATUS_NAME_BRB]) ||
532                                 ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_BRB]] == NSOrderedSame))
533                                 statusID = "brb";
534                         else if (([statusName isEqualToString:STATUS_NAME_BUSY]) ||
535                                          ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_BUSY]] == NSOrderedSame))
536                                 statusID = "busy";
537                         else if (([statusName isEqualToString:STATUS_NAME_PHONE]) ||
538                                          ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_PHONE]] == NSOrderedSame))
539                                 statusID = "phone";
540                         else if (([statusName isEqualToString:STATUS_NAME_LUNCH]) ||
541                                          ([statusMessageString caseInsensitiveCompare:[[adium statusController] localizedDescriptionForCoreStatusName:STATUS_NAME_LUNCH]] == NSOrderedSame))
542                                 statusID = "lunch";
544                         break;
545                         
546                 case AIInvisibleStatusType:
547                 case AIOfflineStatusType:
548                         break;
549         }
550         
551         //If we didn't get a purple status ID, request one from super
552         if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];
554         return statusID;
557 #pragma mark Contact List Menu Items
558 - (NSString *)titleForContactMenuLabel:(const char *)label forContact:(AIListContact *)inContact
560         if ((strcmp(label, _("Initiate Chat")) == 0) || (strcmp(label, _("Initiate _Chat")) == 0)) {
561                 return [NSString stringWithFormat:AILocalizedString(@"Initiate Multiuser Chat with %@",nil),[inContact formattedUID]];
563         } else if (strcmp(label, _("Send to Mobile")) == 0) {
564                 return [NSString stringWithFormat:AILocalizedString(@"Send to %@'s Mobile",nil),[inContact formattedUID]];
565         }
566         
567         return [super titleForContactMenuLabel:label forContact:inContact];
570 #pragma mark Account Action Menu Items
571 - (NSString *)titleForAccountActionMenuLabel:(const char *)label
572 {       
573         if (strcmp(label, _("Set Friendly Name...")) == 0) {
574                 /* Don't include the Set Friendly Name action since we have our own UI for this */
575                 return nil;
577         } else if (strcmp(label, _("Set Home Phone Number...")) == 0) {
578                 return [AILocalizedString(@"Set Home Phone Number",nil) stringByAppendingEllipsis];
579                 
580         } else if (strcmp(label, _("Set Work Phone Number...")) == 0) {
581                 return [AILocalizedString(@"Set Work Phone Number",nil) stringByAppendingEllipsis];
582                 
583         } else if (strcmp(label, _("Set Mobile Phone Number...")) == 0) {
584                 return [AILocalizedString(@"Set Mobile Phone Number",nil) stringByAppendingEllipsis];
585                 
586         } else if (strcmp(label, _("Allow/Disallow Mobile Pages...")) == 0) {
587                 return [AILocalizedString(@"Allow/Disallow Mobile Pages","Action menu item for MSN accounts to toggle whether Mobile pages [forwarding messages to a mobile device] are enabled") stringByAppendingEllipsis];
589         }
591         return [super titleForAccountActionMenuLabel:label];
594 @end